 /*
  (C) 2005 Adam D. Bradley

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
  This class provides simple wrappers around a complete HTTP client implementation.
*/

package http;

import http.Status;
import http.Request;
import java.net.*;

import snobject.MimeTypes;
import snobject.snCommand;
import snobject.snObject;
import java.io.IOException;

public class Client
{
	protected Client(Request Req) {
		value = null;
		raw_value = null;
		request = Req;
	}
	
	public static snObject Perform(Request req) 
	throws Status, IOException {
		System.err.println("Instigating Blocking Request: " + 
				   req.method + " " +
				   req.target.toString());
		Client z = new Client(req);
		z.Perform();
		assert(z.Done());
		return z.Value();
	}

	public static snObject GET(URI uri) 
	throws Status, IOException {
		Client z = new Client(Request.GET(uri, null, null));
		z.Perform();
		assert(z.Done());
		return z.Value();
	}

	public static RawResult GET_raw(URI uri) 
	throws Status, IOException {
		Client z = new Client(Request.GET(uri, null, null));
		z.Perform_raw();
		//System.err.println("back from request");
		return z.RawValue();
	}
	
	// SJJ - SMOD: Needed the possibility to make a request with all the parameters
	public static snObject GET(URI uri, String[] accept, String[] expect) 
	throws Status, IOException {
		Client z = new Client(Request.GET(uri, accept, expect));
		z.Perform();
		assert(z.Done());
		return z.Value();
	}
	
	public static snObject POST(URI uri, snObject body) 
	throws Status, IOException {
		String s[] = {MimeTypes.COMMAND, "text/xml", "text/plain"};
		Client z = new Client(Request.POST(uri, body, s, null));
		z.Perform();
		assert(z.Done());
		return z.Value();
	}
	
	public static snObject PUT(URI uri, snObject body) 
	throws Status, IOException {
		Client z = new Client(Request.PUT(uri, body, null, null));
		z.Perform();
		assert(z.Done());
		return z.Value();
	}
	
	public static snObject DELETE(URI uri) 
	throws Status, IOException {
		Client z = new Client(Request.DELETE(uri, null, null));
		z.Perform();
		assert(z.Done());
		return z.Value();
	}
	
	/*********************************************************************/
	/* Helper function                                                   */
	public static byte[] drainToByteArray(java.io.InputStream is)
	throws java.io.IOException {
		byte[] buf;
		int sz = 65536;
		int bufc = 0;
		buf = new byte[sz];
		do {
			int z = is.read(buf, bufc, 65536 - bufc);
			if (z==-1) {
				byte[] rbuf = new byte[bufc];
				System.arraycopy(buf, 0, rbuf, 0, bufc);
				return rbuf;
			}
			bufc += z;
			if (bufc == sz) {
				byte[] nb = new byte[sz * 2];
				System.arraycopy(buf, 0, nb, 0, sz);
				buf = nb;
				sz *= 2;
			}
		} while (true);
	}

	public static String drainToString(java.io.InputStream is)
	throws java.io.IOException {
		return new String(drainToByteArray(is));
	}

	/*********************************************************************/
	/* Blocking implementation                                           */
	protected void Perform() throws Status, java.io.IOException {
		value = http.Client.implementation(request);
	}

	protected void Perform_raw() throws Status, java.io.IOException {
		raw_value = http.Client.implementation_raw(request);
	}

	/*********************************************************************/
	/* Client thread                                                     */
	protected static snObject implementation(http.Request req) 
	throws Status, java.io.IOException {
		System.err.println(System.currentTimeMillis() + ": Executing Non-blocking Request: " + 
				   req.method + " " +
				   req.target.toString());
		HttpURLConnection c;
		try {
			c = (java.net.HttpURLConnection)
				req.target.toURL().openConnection();
		} catch (ClassCastException ex) {
			throw new IOException("Non-HTTP stream");
		}
		c.setInstanceFollowRedirects(true);
		c.setRequestMethod(req.method);
		c.setRequestProperty("User-Agent", 
				     "snbench-http-client/0.20050720");
		c.setDoInput(true);
		if ((req.accept != null) &&
		    (req.accept.length > 0)) {
			String z = "";
			for (int i=0 ; i<req.accept.length ; i++) {
				if (i!=0)
					z += ", ";
				z += req.accept[i];
			}
			c.setRequestProperty("Accept", z);
		}
		if ((req.expect != null) &&
		    (req.expect.length > 0)) {
			String z = "";
			for (int i=0 ; i<req.expect.length ; i++) {
				if (i!=0)
					z += ", ";
				z += req.expect[i];
			}
			c.setRequestProperty("Expect", z);
		}

		java.util.Enumeration e = req.headers.propertyNames();
		while (e.hasMoreElements()) {
			String hdr = (String) e.nextElement();
			c.setRequestProperty(hdr,
					     req.headers.getProperty(hdr));
		}

		if (req.payload != null) {
			c.setDoOutput(true);
			c.setRequestProperty("Content-Type",
					     req.payloadType);
			java.io.OutputStream os = c.getOutputStream();
			os.write(req.payload, 0, req.payload.length);
		}
		c.connect();

		java.io.InputStream is = c.getInputStream();

		if ((c.getResponseCode() >= 200) &&
		    (c.getResponseCode() <= 299)) {
			Status st = new Status(c.getResponseCode(),
					       c.getResponseMessage(),
					       is,
					       req.target);
			try {
				return snObject.Parse(
					c.getContentType(),
					st.GetPayload());
			} catch (step.parse.SyntaxError ex) {
				throw http.Status.NotAcceptable(
					"Message body isn't valid " +
					c.getContentType());
			} catch (snobject.MimeException ex) {
				/** FIX ME: This error is daft when receiving a 201 OK ... */
				if(c.getResponseCode()==201)
					return snCommand.OK;

				throw http.Status.NotAcceptable(
					"Can't handle type " +
					c.getContentType());
			}
		} else {
			throw new Status(c.getResponseCode(),
					 c.getResponseMessage(),
					 drainToString(is),
					 req.target);
		}
	}
	/*********************************************************************/
	/* Client thread                                                     */
	protected static RawResult implementation_raw(http.Request req) 
	throws Status, java.io.IOException {
		System.err.println(System.currentTimeMillis() + ": Executing Non-blocking Request: " + 
				   req.method + " " +
				   req.target.toString());
		HttpURLConnection c;
		try {
			c = (java.net.HttpURLConnection)
				req.target.toURL().openConnection();
		} catch (ClassCastException ex) {
			throw new IOException("Non-HTTP stream");
		}
		c.setInstanceFollowRedirects(true);
		c.setRequestMethod(req.method);
		c.setRequestProperty("User-Agent", 
				     "snbench-http-client/0.20050720");
		c.setDoInput(true);
		if ((req.accept != null) &&
		    (req.accept.length > 0)) {
			String z = "";
			for (int i=0 ; i<req.accept.length ; i++) {
				if (i!=0)
					z += ", ";
				z += req.accept[i];
			}
			c.setRequestProperty("Accept", z);
		}
		if ((req.expect != null) &&
		    (req.expect.length > 0)) {
			String z = "";
			for (int i=0 ; i<req.expect.length ; i++) {
				if (i!=0)
					z += ", ";
				z += req.expect[i];
			}
			c.setRequestProperty("Expect", z);
		}

		java.util.Enumeration e = req.headers.propertyNames();
		while (e.hasMoreElements()) {
			String hdr = (String) e.nextElement();
			c.setRequestProperty(hdr,
					     req.headers.getProperty(hdr));
		}

		if (req.payload != null) {
			c.setDoOutput(true);
			c.setRequestProperty("Content-Type",
					     req.payloadType);
			java.io.OutputStream os = c.getOutputStream();
			os.write(req.payload, 0, req.payload.length);
		}
		c.connect();

		java.io.InputStream is = c.getInputStream();

		if ((c.getResponseCode() >= 200) &&
		    (c.getResponseCode() <= 299)) {
			Status st = new Status(c.getResponseCode(),
					       c.getResponseMessage(),
					       is,
					       req.target);
				return new http.RawResult(
					c.getContentType(),
					st.GetPayload());
		} else {
			throw new Status(c.getResponseCode(),
					 c.getResponseMessage(),
					 drainToString(is),
					 req.target);
		}
	}

	/*********************************************************************/
	/* Non-blocking interface */
	public static Client Perform_nb(Request req) {
		System.err.println("Instigating Non-Blocking Request: " + 
				   req.method + " " +
				   req.target.toString());
		return new http.ThreadedClient(req);
	}

	public static Client GET_nb(URI uri) {
		Request r = Request.GET(uri, null, null);
		return Perform_nb(r);
	}

	public static Client POST_nb(URI uri, snObject object) {
		Request r = Request.POST(uri, object, null, null);
		return Perform_nb(r);
	}

	public static Client PUT_nb(URI uri, snObject object) {
		Request r = Request.PUT(uri, object, null, null);
		return Perform_nb(r);
	}

	public static Client DELETE_nb(URI uri) {
		Request r = Request.DELETE(uri, null, null);
		return Perform_nb(r);
	}


	public boolean Done() { return true; }
	public snObject Value() { return value; }
	protected RawResult RawValue() { return raw_value; }

	Request request;
	snObject value;
	RawResult raw_value;
}
